package com.cirnoworks.uhs.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cirnoworks.uhs.AddressCache;
import com.cirnoworks.uhs.AddressKey;
import com.cirnoworks.uhs.Link;
import com.cirnoworks.uhs.LinkManager;
import com.cirnoworks.uhs.Protocal;

public class ClientLink extends Link {
	private Logger LOG = LoggerFactory.getLogger(ClientLink.class);

	SocketAddress handShakerAddress;
	SocketAddress targetAddress;
	final int servicePort;
	final int redirectorPort;
	final AddressKey addressKey = new AddressKey();
	byte[] userKey;
	final LinkManager manager;

	HashMap<SocketAddress, ClientSubLink> portmapSessions = new HashMap<SocketAddress, ClientSubLink>();
	HashMap<SocketAddress, ClientRedirectSublink> redirectSessions = new HashMap<SocketAddress, ClientRedirectSublink>();

	public ClientLink(Selector selector, LinkManager manager, byte[] userKey,
			String targetHost, int targetPort, int redirectorPort,
			int servicePort) throws IOException {
		super(selector, manager);
		this.manager = manager;
		this.userKey = userKey;
		targetAddress = new InetSocketAddress(targetHost, targetPort);
		this.servicePort = servicePort;
		this.redirectorPort = redirectorPort;
		channel.socket().bind(new InetSocketAddress(servicePort));
	}

	protected void receiveData(Selector selector, SocketAddress address,
			ByteBuffer buf) throws IOException {
		if (handShakerAddress != null && handShakerAddress.equals(address)) {
			int op = buf.get();
			switch (op) {
			case Protocal.CmdPortMap.OP:
				// long seq = buf.getLong();
				// byte code = buf.get();
				// LOG.info("Portmap response from handshaker "
				// + handShakerAddress + " seq=" + seq + " code=" + code);
				break;
			case Protocal.RedirectPayload.OP:
				addressKey.setAddress(buf.getLong());
				addressKey.setPort(buf.getChar());
				SocketAddress clientAddress = AddressCache
						.getAddress(addressKey);

				ClientRedirectSublink session = redirectSessions
						.get(clientAddress);
				if (session == null) {
					session = new ClientRedirectSublink(selector, manager,
							this, targetAddress, handShakerAddress,
							clientAddress, userKey, (char) servicePort);
					redirectSessions.put(clientAddress, session);
				}

				session.sendData(selector, buf);
				break;
			default:
				buf.position(0);
				LOG.warn("Unknown message from handshaker " + handShakerAddress
						+ " " + Protocal.toString(buf));
				break;
			}
		} else {

			ClientSubLink session = portmapSessions.get(address);
			if (session == null) {
				session = new ClientSubLink(this, selector, manager,
						targetAddress, address);
				portmapSessions.put(address, session);
			}
			session.sendData(selector, buf);
		}
	}

	public void checkSessions() {
		Iterator<ClientSubLink> iSessionPortmap = portmapSessions.values()
				.iterator();
		while (iSessionPortmap.hasNext()) {
			ClientSubLink session = iSessionPortmap.next();
			if (session.isTimeout()) {
				try {
					session.close();
				} catch (Exception e) {
				}
				iSessionPortmap.remove();
			}
		}

		Iterator<ClientRedirectSublink> iSessionRedirect = redirectSessions
				.values().iterator();
		while (iSessionRedirect.hasNext()) {
			ClientRedirectSublink session = iSessionRedirect.next();
			if (session.isTimeout()) {
				try {
					session.close();
				} catch (Exception e) {
				}
				iSessionRedirect.remove();
			}
		}
	}

	@Override
	public void onClose() {
		for (ClientSubLink sublink : portmapSessions.values()) {
			try {
				sublink.close();
			} catch (Exception e) {
			}
		}
		portmapSessions.clear();
		for (ClientRedirectSublink sublink : redirectSessions.values()) {
			try {
				sublink.close();
			} catch (Exception e) {
			}
		}
		redirectSessions.clear();
	}

	public SocketAddress getHandShakerAddress() {
		return handShakerAddress;
	}

	public void setHandShakerAddress(SocketAddress handShakerAddress) {
		this.handShakerAddress = handShakerAddress;
	}

	public int getServicePort() {
		return servicePort;
	}

	public int getRedirectorPort() {
		return redirectorPort;
	}

}
